package com.free.mini.gateway.service.body;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import javax.annotation.PostConstruct;

import cn.hutool.core.collection.CollectionUtil;
import com.free.mini.common.utils.BizTraceUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.free.mini.common.base.ResultCode;
import com.free.mini.common.base.ResultData;
import com.free.mini.gateway.config.ApiKeysConfig;
import com.free.mini.gateway.constants.GatewayConstant;
import com.free.mini.gateway.filter.GatewayContext;
import com.free.mini.gateway.service.ParseRequestBody;
import com.free.mini.gateway.utils.DataDealUtil;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author dongsufeng
 * @version 4.0
 * @date 2023/8/7 17:39
 */
@Service
@Slf4j
public class JsonContentType implements ParseRequestBody {
    @PostConstruct
    public void init(){
        ParseRequestBody.map.put(MediaType.APPLICATION_JSON.getType(),this);
    }
    @Override
    public Boolean support(GatewayContext gatewayContext) {
        MediaType contentType = gatewayContext.getContentType();
        if (MediaType.APPLICATION_JSON.getType().equals(contentType.getType())
                && MediaType.APPLICATION_JSON.getSubtype().equals(contentType.getSubtype())) {
            if (log.isDebugEnabled()) {
                log.debug("do RequestFilter [Content-Type: application/json].");
            }
            return true;
        }
        return false;
    }

    @Override
    public Mono<Void> parseBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        ServerHttpRequest serverHttpRequest = gatewayContext.getServerHttpRequest();
        return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(dataBuffer -> {
            try {
                /**
                 * 重构请求参数
                 */
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                String body;
                if (gatewayContext.getNeedEncryption()){
                    decryptAndSign( new String(bytes, StandardCharsets.UTF_8), gatewayContext);
                    body = gatewayContext.getParams().toString();
                } else {
                    String req = new String(bytes, StandardCharsets.UTF_8);
                    JSONObject jsonObject = new JSONObject();
                    if (CollectionUtil.isNotEmpty(gatewayContext.getParams())) {
                        jsonObject.putAll(gatewayContext.getParams());
                    }
                    if (StringUtils.isNotBlank(req)){
                        jsonObject.putAll(JSON.parseObject(req));
                    }
                    body = jsonObject.toJSONString();
                }
                if (log.isDebugEnabled()) {
                    log.debug("request body :{}", body);
                }
                byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
                if (bodyBytes.length > 0) {
                    gatewayContext.setContentLength(bodyBytes.length);
                } else {
                    gatewayContext.removeContentLength();
                }

                return chain.filter(exchange.mutate().request(new ServerHttpRequestDecorator(serverHttpRequest) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return Flux.just(exchange.getResponse().bufferFactory().wrap(bodyBytes));
                    }
                    @Override
                    public HttpHeaders getHeaders() {
                        HttpHeaders headers = gatewayContext.getHeaders();
//                        headers.add(BizTraceUtil.BIZ_TRACE_ID,gatewayContext.getBizTraceId());
                        return gatewayContext.getHeaders();
                    }
                }).build());
            } finally {
                DataBufferUtils.release(dataBuffer);
            }
        });
    }

    @Override
    public void decryptAndSign(String reqStr, GatewayContext gatewayContext) {
        if (StringUtils.isBlank(reqStr)) {
            log.warn("extract params by key=data, is null.");
            gatewayContext.setResult(new ResultData(ResultCode.PARAM_ERROR));
            return;
        }
        try {
            gatewayContext.setEncodeParams(reqStr);
            JSONObject object = JSON.parseObject(reqStr);
            String data = reqStr;
            if (object.containsKey(GatewayConstant.DATA)) {
                data = object.getString(GatewayConstant.DATA);
            }
            JSONObject decryptObj = DataDealUtil.decrypt(data, JSONObject.class, true,new ApiKeysConfig());
            // 解密
            if (decryptObj == null) {
                log.warn("decrypt failed, decrypt: null");
                gatewayContext.setResult(new ResultData(ResultCode.PARAM_ERROR));
                return;
            }
            decryptObj.putAll(gatewayContext.getParams());
            object.put(GatewayConstant.DATA, decryptObj);
            gatewayContext.setParams(object);
        } catch (Exception e) {
            log.error("处理解密时异常", e);
            gatewayContext.setResult(new ResultData(ResultCode.SYSTEM_ERROR));
        }
    }
}
